<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="property-accessors.html">

<script>
  (function() {
    'use strict';

    const HOST_DIR = /:host\(:dir\((ltr|rtl)\)\)/g;
    const HOST_DIR_REPLACMENT = ':host([dir="$1"])';

    const EL_DIR = /([\s\w-#\.\[\]\*]*):dir\((ltr|rtl)\)/g;
    const EL_DIR_REPLACMENT = ':host([dir="$2"]) $1';

    /**
     * @type {!Array<!Polymer_DirMixin>}
     */
    const DIR_INSTANCES = [];

    /** @type {MutationObserver} */
    let observer = null;

    let DOCUMENT_DIR = '';

    function getRTL() {
      DOCUMENT_DIR = document.documentElement.getAttribute('dir');
    }

    /**
     * @param {!Polymer_DirMixin} instance Instance to set RTL status on
     */
    function setRTL(instance) {
      if (!instance.__autoDirOptOut) {
        const el = /** @type {!HTMLElement} */(instance);
        el.setAttribute('dir', DOCUMENT_DIR);
      }
    }

    function updateDirection() {
      getRTL();
      DOCUMENT_DIR = document.documentElement.getAttribute('dir');
      for (let i = 0; i < DIR_INSTANCES.length; i++) {
        setRTL(DIR_INSTANCES[i]);
      }
    }

    function takeRecords() {
      if (observer && observer.takeRecords().length) {
        updateDirection();
      }
    }

    /**
     * Element class mixin that allows elements to use the `:dir` CSS Selector to have
     * text direction specific styling.
     *
     * With this mixin, any stylesheet provided in the template will transform `:dir` into
     * `:host([dir])` and sync direction with the page via the element's `dir` attribute.
     *
     * Elements can opt out of the global page text direction by setting the `dir` attribute
     * directly in `ready()` or in HTML.
     *
     * Caveats:
     * - Applications must set `<html dir="ltr">` or `<html dir="rtl">` to sync direction
     * - Automatic left-to-right or right-to-left styling is sync'd with the `<html>` element only.
     * - Changing `dir` at runtime is supported.
     * - Opting out of the global direction styling is permanent
     *
     * @mixinFunction
     * @polymer
     * @appliesMixin Polymer.PropertyAccessors
     * @memberof Polymer
     */
    Polymer.DirMixin = Polymer.dedupingMixin((base) => {

      if (!observer) {
        getRTL();
        observer = new MutationObserver(updateDirection);
        observer.observe(document.documentElement, {attributes: true, attributeFilter: ['dir']});
      }

      /**
       * @constructor
       * @extends {base}
       * @implements {Polymer_PropertyAccessors}
       */
      const elementBase = Polymer.PropertyAccessors(base);

      /**
       * @polymer
       * @mixinClass
       * @implements {Polymer_DirMixin}
       */
      class Dir extends elementBase {

        /**
         * @override
         * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
         */
        static _processStyleText(cssText, baseURI) {
          cssText = super._processStyleText(cssText, baseURI);
          cssText = this._replaceDirInCssText(cssText);
          return cssText;
        }

        /**
         * Replace `:dir` in the given CSS text
         *
         * @param {string} text CSS text to replace DIR
         * @return {string} Modified CSS
         */
        static _replaceDirInCssText(text) {
          let replacedText = text;
          replacedText = replacedText.replace(HOST_DIR, HOST_DIR_REPLACMENT);
          replacedText = replacedText.replace(EL_DIR, EL_DIR_REPLACMENT);
          if (text !== replacedText) {
            this.__activateDir = true;
          }
          return replacedText;
        }

        constructor() {
          super();
          /** @type {boolean} */
          this.__autoDirOptOut = false;
        }

        /**
         * @suppress {invalidCasts} Closure doesn't understand that `this` is an HTMLElement
         * @return {void}
         */
        ready() {
          super.ready();
          this.__autoDirOptOut = /** @type {!HTMLElement} */(this).hasAttribute('dir');
        }

        /**
         * @suppress {missingProperties} If it exists on elementBase, it can be super'd
         * @return {void}
         */
        connectedCallback() {
          if (elementBase.prototype.connectedCallback) {
            super.connectedCallback();
          }
          if (this.constructor.__activateDir) {
            takeRecords();
            DIR_INSTANCES.push(this);
            setRTL(this);
          }
        }

        /**
         * @suppress {missingProperties} If it exists on elementBase, it can be super'd
         * @return {void}
         */
        disconnectedCallback() {
          if (elementBase.prototype.disconnectedCallback) {
            super.disconnectedCallback();
          }
          if (this.constructor.__activateDir) {
            const idx = DIR_INSTANCES.indexOf(this);
            if (idx > -1) {
              DIR_INSTANCES.splice(idx, 1);
            }
          }
        }
      }

      Dir.__activateDir = false;

      return Dir;
    });
  })();
</script>
